using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using RoardersAgentSim.Map;
using System.IO;

namespace RoardersAgentSim.Agent
{
    /// <summary>
    /// Decision makers agents
    /// </summary>
    public partial class DecisionMakers // government
    {
        #region input attributes
        /// <summary>
        /// threshold to reconsider optimization
        /// </summary>
        public double theta;
        #endregion
        private Dictionary<Shelter, int> shelterAdviceAvailability;
        private Dictionary<Node, Dictionary<Node, LinkPath>> car_paths = new Dictionary<Node, Dictionary<Node, LinkPath>>();
        private Dictionary<Node, Dictionary<Node, LinkPath>> walking_paths = new Dictionary<Node, Dictionary<Node, LinkPath>>();
        #region public methods
        /// <summary>
        /// Initialization
        /// </summary>
        public void Initialize()
        {
            // theta
            LogHelper.LogStep("Decision makers initialized.");
        }
        /// <summary>
        /// find the optimal response
        /// </summary>
        public void FindOptimalResponse() // A2.1
        {
            //todo
            LogHelper.LogStep("FindOptimalResponse - A2.1");
            //! calculate the closest shelter for each zone
            //Regarding shelter advice.
            //following solutions in decreasing complexity ...
            //a) the adviser either builds a zone centroid, i.e. find the (by population weighted) geometric centre of a zone, assume all demand of the zone is concentrated here and then create artifical access links to the surrounding nodes (this is I think what the software does that Joe presented).
            //b) find the node that is nearest the geometric centre and assume all demand is leaving from there (i.e. no artificial access links)
            //c) simply assume all demand is leaving from the node with highest population within the zone

            Dictionary<int, Node> centroidNodeList = new Dictionary<int, Node>();
            shelterAdviceAvailability = new Dictionary<Shelter, int>();
            //data structure used to remember how many groups can be sent to that specific shelter

            foreach (Shelter s in shelterManager.Shelters)
            {
                shelterAdviceAvailability.Add(s, s.GroupsAvailable);
            }

            foreach (Zone zone in environment.CurrentMap.Zones)
            {
                //calculate the centroid node for each zone
                int max = -1;
                Node centroid = null;
                foreach (Node n in environment.CurrentMap.Nodes.Where(x => x.Zone == zone))
                {
                    int temp = populationManager.GetPopulationOnNode(n);
                    if (max < temp)
                    {
                        max = temp;
                        centroid = n;
                    }
                }
                //centroidNodeList.Add(z.ZoneID, centroid);
                Advice advice = new Advice();

                advice.e_a = 1;
                var queryCar = this.car_paths[centroid];
                var queryWalk = this.walking_paths[centroid];

                Shelter carSelectedShelter = null;
                LinkPath carSelectedLP = null;
                Shelter walkSelectedShelter = null;
                LinkPath walkSelectedLP = null;
                double minCar = int.MaxValue;
                double minWalk = int.MaxValue;

                foreach (Shelter shelter in shelterManager.Shelters.Where(x => shelterAdviceAvailability[x] > 0 /*&& shelterAdviceAvailability[x] >= populationManager.GetGroupsInZone(zone)*/))
                {
                    if (centroid == shelter.Node)
                    {
                        advice.e_a = 0;
                        break;
                    }
                    if (queryCar != null && queryCar.Any())
                    {
                        LinkPath lp = queryCar[shelter.Node];
                        if (lp != null)
                        {
                            double sum = lp.RoadLinks.Sum(x => x.Link.Length);
                            if (minCar > sum)
                            {
                                carSelectedShelter = shelter;
                                carSelectedLP = lp;
                                minCar = sum;
                            }
                            foreach (RoadLink rl in environment.CurrentMap.CarRoadLinks.Where(x => x.Link.H == zone))
                            {
                                rl.g_car.Add(shelter.ShelterID, sum);
                            }
                        }
                    }
                    if (queryWalk != null && queryWalk.Any())
                    {
                        LinkPath lp = queryWalk[shelter.Node];
                        if (lp != null)
                        {
                            double sum = lp.RoadLinks.Sum(x => x.Link.Length);
                            if (minWalk > sum)
                            {
                                walkSelectedShelter = shelter;
                                walkSelectedLP = lp;
                                minWalk = sum;
                            }
                            foreach (RoadLink rl in environment.CurrentMap.WalkRoadLinks.Where(x => x.Link.H == zone))
                            {
                                rl.g_walk.Add(shelter.ShelterID, sum);
                            }
                        }
                    }
                }
                if ((carSelectedShelter != null && carSelectedLP != null) || (walkSelectedShelter != null && walkSelectedLP != null))
                {
                    //optimal shelter was found
                    if (minCar < minWalk)
                    {
                        advice.P_a = carSelectedLP;
                        advice.S_a = new List<Shelter>();
                        advice.S_a.Add(carSelectedShelter);
                        shelterAdviceAvailability[carSelectedShelter] -= populationManager.GetGroupsInZone(zone);

                        adviceManager.Add(zone.ZoneID, advice);
                    }
                    else
                    {
                        advice.P_a = walkSelectedLP;
                        advice.S_a = new List<Shelter>();
                        advice.S_a.Add(walkSelectedShelter);
                        shelterAdviceAvailability[walkSelectedShelter] -= populationManager.GetGroupsInZone(zone);
                        adviceManager.Add(zone.ZoneID, advice);
                    }
                }
                LogHelper.LogStep("Found advice " + advice.Description + " for zone " + zone.Description);

            }
            bool future = false;
            if (future)
            {
                AdviseZone();
                AssignPopulationToShelters();
                AdvisePopulationToShortestPath();
            }
        }
        /// <summary>
        /// Decide if the advice is to be reconsidered
        /// </summary>
        public void DecideIfReconsideringAdvice() // A2.2
        {

            //future implementation
            bool future = false;
            if (future)
            {
                LogHelper.LogStep("DecideIfReconsideringAdvice - A2.2");
                //if theta reached or shepter capacity reached
                if (this.shelterManager.zr_IsEmpty() || this.roadLinksManager.Deviation() > this.theta)
                {
                    // call AssignPopulationToShelters, AdvisePopulationToShortestPath
                    AssignPopulationToShelters();
                    AdvisePopulationToShortestPath();
                }
            }
        }

        /// <summary>
        /// Compute shortest path
        /// </summary>
        public void ComputeShortestPaths() // A2.3
        {

            car_paths = new Dictionary<Node, Dictionary<Node, LinkPath>>();
            walking_paths = new Dictionary<Node, Dictionary<Node, LinkPath>>();
            try
            {
                LogHelper.LogStep("Computing shortest paths - A2.3");
                // compute shortest paths
                //for all population to all shelters, compute the shortest path
                //run DIJKSTRA from their initial node to all shelters
                //! from every node to what each shelter node node
                foreach (Node n in environment.CurrentMap.Nodes)
                {
                    LogHelper.LogStep("For node " + n.NodeID, true);
                    Dictionary<Node, LinkPath> temp = Algorithms.ComputeShortestPath(environment.CurrentMap, n, LinkType.CAR_LINK);
                    foreach (Node to in temp.Keys)
                    {
                        LogHelper.LogStep("To " + to.NodeID + " via " + temp[to].Description,true);
                    }
                    car_paths.Add(n, temp);

                    temp = Algorithms.ComputeShortestPath(environment.CurrentMap, n, LinkType.WALK_LINK);
                    foreach (Node to in temp.Keys)
                    {
                        LogHelper.LogStep("To " + to.NodeID + " via " + temp[to].Description, true);
                    }
                    walking_paths.Add(n, temp);
                }
            }
            catch (Exception ex)
            {

                LogHelper.LogException(ex);
            }
        }
        #endregion

        #region private methods
        /// <summary>
        /// advise the zone
        /// </summary>
        private void AdviseZone() // A2.1.1
        {
            LogHelper.LogStep("AdviceArea - A2.1.1");

            //roadLinks.Access("d_h, g");
            //environment.Access("tau_z");
            // compute e_a

        }
        /// <summary>
        /// Assign the population to shelters
        /// </summary>
        private void AssignPopulationToShelters() // A2.1.2
        {
            LogHelper.LogStep("AssignPopulationToShelters - A2.1.2");

            //population.Access("h, eta");
            //advice.Access("e_a");
            //roadLinks.Access("g, q");
            //shelters.Access("psi");
            // solve linear program => q_a
            //advice.Access("set: advice.q_a");
            // compute S_a
            //advice.Access("set: advice.S_a");
        }
        /// <summary>
        /// Tell the population which is the shortest path
        /// </summary>
        private void AdvisePopulationToShortestPath() // A2.1.3
        {
            LogHelper.LogStep("AdvisePopulationToShortestPath - A2.1.3");

            //advice.Access("q_a");
            // compute P_a
            //advice.Access("set: advice.P_a");
        }
        #endregion private methods
        #region export

        internal void ExportPerformance()
        {
            string directory = Utils.GetOutputDirectory();
            string filename = string.Format("{0}_{1}.{2}", Utils.FilePrefix, "advice_quality", "csv");
            StreamWriter file = new StreamWriter(directory + filename, true);
            file.WriteLine(GetPerformanceHeader());
            foreach (int key in this.adviceManager.Keys)
            {
                Zone z = environment.CurrentMap.Zones.FirstOrDefault(x => x.ZoneID == key);
                Advice advice = adviceManager[key];
                int advised = 0;
                int evacuas = 0;//number of individuals which listened to the advice
                if (z != null && advice.S_a != null)
                {
                    var items = this.populationManager.GetPopulationInitiallyInZone(z);
                    if (items != null)
                    {
                        advised = items.Sum(x => x.PeopleCount);
                        var temp = items.Where(x => x.Shelter == advice.S_a[0]);
                        if (temp != null)
                            evacuas = temp.Sum(x => x.PeopleCount);

                    }
                    double result = (double)advised/evacuas;
                    file.WriteLine(key.ToString() + Utils.GetCSVDelimiter() + advice.S_a[0].Node.NodeID + Utils.GetCSVDelimiter() + advised.ToString() + Utils.GetCSVDelimiter() + evacuas.ToString() + Utils.GetCSVDelimiter() + result.ToString());
                }
                
            }
            file.Close();
        }

        private string GetPerformanceHeader()
        {
            string header = "ZoneId" + Utils.GetCSVDelimiter() + "Shelter_Node" + Utils.GetCSVDelimiter() + "Advised" + Utils.GetCSVDelimiter() + "Evacuas" + Utils.GetCSVDelimiter() + "QUALITY";
            return header;
        }
        #endregion


    }
}
